<?php

namespace TraceLinks;

use App\Exception\BizException;
use Hyperf\Database\Schema\Schema;
use Hyperf\DbConnection\Db;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
use Monolog\Logger;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Context\Context;
use Hyperf\Database\Schema\Blueprint;
class LoggerMiddleware implements MiddlewareInterface
{
    protected RequestInterface $request;
    /**
     * @var ContainerInterface
     */
    protected $container;

    public function __construct(ContainerInterface $container,RequestInterface $request)
    {
        $this->request = $request;
        $this->container = $container;

    }

    private function ensureTraceLinksTable(): void
    {
        if (Schema::hasTable('trace_links')) {
            return;
        }

        Schema::create('trace_links', function (Blueprint $table) {
            $table->comment('api请求追踪链路表：记录接口请求的详细信息（参数、SQL、Redis、错误等）');
            $table->increments('id');
            $table->string('request_id', 60)->default('')->comment('请求id(nginx传入)');
            $table->unsignedInteger('operator_id')->default(0)->comment('操作员id');
            $table->string('operator', 50)->default('')->comment('操作员');
            $table->unsignedTinyInteger('rank')->default(1)->comment('级别:1=info,2=error');
            $table->string('uri', 200)->default('')->comment('请求uri');
            $table->string('method', 10)->default('')->comment('请求方法');
            $table->string('ip', 200)->default('')->comment('ip');
            $table->text('params')->comment('参数');
            $table->unsignedInteger('duration')->default(0)->comment('请求时间(ms)');
            $table->text('sqls')->comment('sql结果');
            $table->text('redis')->comment('redis结果');
            $table->text('throw_error')->comment('throw_error结果');
            $table->string('response_code', 5)->default('')->comment('响应code');
            $table->text('response_body')->comment('响应body');
            $table->dateTime('request_time')->nullable()->comment('请求时间');
            $table->dateTime('created_at')->useCurrent();
            // 索引（可选）
            $table->index('operator_id');
            $table->index('request_time');
        });
    }

    /**
     * 写数据
     * @param array $payload
     * @return void
     */
    private function writeTraceLink(array $payload): void
    {
        // 长文本裁剪，避免 TEXT 溢出（~64KB）；也可改成 MEDIUMTEXT
        $limit = 65000;

        $data = [
            'request_id' => $payload['request_id'],
            'operator_id'   => (int)($payload['operator_id'] ?? 0),
            'operator'      => (string)($payload['operator'] ?? ''),
            'uri'           => (string)($payload['uri'] ?? ''),
            'method'        => (string)($payload['method'] ?? ''),
            'ip'            => (string)($payload['ip'] ?? ''),
            'params'        => mb_strimwidth(json_encode($payload['params'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 0, $limit, '...', 'UTF-8'),
            'duration'      => (int)($payload['duration'] ?? 0),
            'sqls'          => mb_strimwidth(json_encode($payload['sqls'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 0, $limit, '...', 'UTF-8'),
            'redis'         => mb_strimwidth(json_encode($payload['redis'],JSON_UNESCAPED_UNICODE), 0, $limit, '...', 'UTF-8'),
            'throw_error'   => mb_strimwidth(json_encode($payload['throw_error'],JSON_UNESCAPED_UNICODE), 0, $limit, '...', 'UTF-8'),
            'response_code' => (string)($payload['response_code'] ?? ''),
            'response_body' => mb_strimwidth((string)$payload['response_body'], 0,  $limit, '...', 'UTF-8'),
            'request_time'  => $payload['request_time'] ?? date('Y-m-d H:i:s'),
            'created_at'    => date('Y-m-d H:i:s'),
        ];

        if(!empty($payload['throw_error'])){
            $data['rank'] = 2;
        }

        try {
            Db::table('trace_links')->insert($data);
        } catch (\Throwable $e) {
            // 写库失败时回退到日志文件，避免丢数据
            $this->logger->error('写 trace_links 失败：' . $e->getMessage(), $data);
        }
    }

    /**
     * 确认表不存在 存在即创建
     * @param ServerRequestInterface $request
     * @param RequestHandlerInterface $handler
     * @return ResponseInterface
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $start = microtime(true);

        $response = $this->container->get(HttpResponse::class);

        $responseBody = '';

        $requestId = $request->getHeaderLine('X-Request-ID') ?: uniqid('hyperf_'); // 兜底：若没传则生成

        // 将nginx请求ID存入请求上下文（方便后续业务逻辑调用）
        $request = $request->withAttribute('request_id', $requestId);

        try {
            $response = $handler->handle($request);

            $code = $response->getStatusCode();

            if ($response && $response->getBody() instanceof SwooleStream) {
                $responseBody = (string) $response->getBody();
//                $responseBody = config('app_env') !== 'prod' ? $responseBody : '[hidden]';
                $responseBody =  mb_substr($responseBody, 0, 2000);
            }


            return $response;
        }catch (\Throwable $e){
            $code = $response->getStatusCode();

            if($code == 500){
                $msg = 'internal server error';
            }else{
                $body = json_decode($e->getMessage(),true);

                $msg = $body['message']??'系统异常,请稍后重试';
            }

            $d = [
                'code' => $code,
                'message' => $msg,
            ];

            $logs[] = [
                'info'   => $e->getMessage(),
                'trace'  => $e->getTrace(),
            ];

            Context::set('throwError.logs', $logs);

            $responseBody = json_encode($d,JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

            return $response->withHeader('X-Request-ID', $requestId)->json($d);

        } finally {
            $duration = round((microtime(true) - $start) * 1000, 2) . ' ms';
            $sqls = Context::get('sql.logs', []);

            $redisLogs = Context::get('redis.logs', []);

            // 这里要处理一下redis 日志的值
            foreach ($redisLogs as &$log) {

                if (is_string($log['result'])) {
                    $log['result'] =  json_decode($log['result'], true);
                }

                if( $log['method'] == 'setex'  && isset($log['arguments'][2]) && is_string($log['arguments'][2])) {
                    $log['arguments'][2] =  json_decode($log['arguments'][2], true);
                }

            }

            $throwError = Context::get('throwError.logs', []);

            $userInfo = Context::get('user_info', []); // 操作员

            $this->ensureTraceLinksTable(); // 确保存在（仅首次建）


            // 将下面的原来写入日志文件的数据插入表

            $this->writeTraceLink([
                'request_time'  => date('Y-m-d H:i:s'),
                'request_id' => $requestId,
                'method'        => $this->request->getMethod(),
                'uri'           => (string)$this->request->getUri(),
                'ip'            => $this->request->getServerParams()['remote_addr'] ?? 'unknown',
                'operator'      => $userInfo ? $userInfo['name'] : '',
                'operator_id'   => $userInfo ? ($userInfo['id'] ?? 0) : 0,
                'params'        => $this->request->all(),
                'duration'      => $duration,
                'sqls'          => $sqls,        // 数组/字符串都行，会 JSON 化
                'redis'         => $redisLogs,   // 同上
                'throw_error'   => $throwError,   // 同上
                'response_code' => $code,
                'response_body' => $responseBody, // 已读取字符串
            ]);


        }


    }
}
